iT邦幫忙

2021 iThome 鐵人賽

DAY 14
1
自我挑戰組

資料分析及AI深度學習-簡單基礎實作系列 第 14

DAY14:玉山人工智慧挑戰賽-中文手寫字辨識(OpenCV圖像處理)

  • 分享至 

  • xImage
  •  

問題及解決方法

  • 用YOLOv4模型裁切出來的文字,大部分的圖檔,都有紅框等雜訊的存在,如下圖。若將含有雜訊的圖檔丟進模型訓練,可能會造成失焦而影響準確度。
      
    解決:我們選擇以HSV來做顏色的追蹤,以抓取紅框雜訊為主。
  • 圖檔的中文字有藍色及黑色居多,字體顏色有可能也會影響我們的模型準確度。
    解決:採取將圖片灰階的方法,就沒有顏色的影響。

操作內容

  • HSV(Hue, Saturation, Value)

    • 由色調(Hue)、飽和度(Saturation)、亮度(Value)來組成,可表示維下圖的圓錐體。

      圖片來源:https://read01.com/6mD5Kx.html#.YVQfV5pByUk
      • 色調:即為顏色。
      • 飽和度:色彩的純度,若值越高越純,反之則會略顯灰。
      • 亮度:即為顏色亮度,值越大越亮,若值為0則呈現黑色。
    • HSV也是依賴RGB,紅色、綠色及藍色,是一個顏色空間,方便我們識別。
  • HSV+OpenCV

    • 透過OpenCV可以使用內建函數來將RGB轉換成HSV。
    # 將RGB轉換成HSV顏色空間
      redhsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
    • 轉換成HSV後就可以來將特定的顏色找出來,助於我們找出紅框的顏色,加以去除。

    • 首先,我們需要用到HSV的調色盤,才抓到圖檔每個點的值是多少。以下圖為例,我點選左右兩點的紅線,他則可以顯示出我們需要的HSV值為多少。

      import cv2
      import numpy as np
    
      # 讀取中文路徑圖檔(圖片讀取為BGR)
      def cv_imread(filePath):
          cv_img = cv2.imdecode(np.fromfile(filePath, dtype=np.uint8), -1)
          return cv_img
    
      # 點擊欲判定HSV值的圖片位置(以滑鼠左鍵單擊)
      def mouse_click(event, x, y, flags, para):
          if event == cv2.EVENT_LBUTTONDOWN:
              print("BGR:", img[y, x])
              print("GRAY:", gray[y, x])
              print("HSV:", hsv[y, x])
              print('='*30)
    
      if __name__ == '__main__':
          # 讀取圖檔
          img = cv_imread('./pic/1_經.jpg')
          img = cv2.resize(img, (320, 240))
          # 轉換成gray與HSV
          gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
          hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
          cv2.namedWindow("img")
          cv2.setMouseCallback("img", mouse_click)
          while True:
              cv2.imshow('img', img)
              if cv2.waitKey() == ord('q'):
                  break
          cv2.destroyAllWindows()
    
    

    • 透過這樣點選的方式,可以找出紅線的值,我們就可以將它取出。我們先鎖定紅線,慢慢的點,大概有個閾值出來。下列程式碼為追蹤紅線。

      def red1_mask(img):
          #紅1
          lower = np.array([150,80,94])
          upper = np.array([180,255,255])
          redhsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
          mask1 = cv2.inRange(redhsv1, lower, upper)
          return mask1
      

      白色是我們追蹤到的紅線部分,發現右邊還有些紅線尚未被追蹤到。

    • 於是我們要試著把右邊紅線的部分,看能否追蹤到多一點。依照上述步驟繼續點選紅線位置,找出他的閾值,下列程式碼為追蹤第二段紅線。

      def red2_mask(img):
      #紅2
      lower = np.array([0,80,89])
      upper = np.array([10,255,255])
      redhsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
      mask = cv2.inRange(redhsv, lower, upper)
      return mask
      

      可以發現我們追蹤到上面沒追蹤到的紅色部分,還缺少最右邊的紅色線條,因為他有跟中文字重疊。

    • 接下來我們將追蹤到的兩個紅色線條合起來。

      #紅1
      mask1 = red1_mask(img)
      #紅2
      mask2 = red2_mask(img)
      #紅1 + 紅2 範圍
      mask3 = cv2.bitwise_or(mask1,mask2)
      

      透過bitwise_or取聯集,使兩張圖合併在一起。

    • 取出紅線之後,我們需要將他去除掉,也就是用背景顏色去填補,在這之前我們先將他膨脹,然後白色的地方轉成黑色的。

      def my_dilate(img):
          kernel = np.ones((3, 3), np.uint8)
          new_img = cv2.dilate(img, kernel, iterations=1)
          return new_img
      
      # 膨脹mask3
      mask3 = my_dilate(mask3)
      #黑白反轉
      mask3 = cv2.bitwise_not(mask3,mask3)
      


      黑框是特別截圖下來,以示整張圖片,主要是白色變黑色。

    • 先做一個動作,將原本的圖片做灰階,再來就是黑色部份我們到時候要用背景顏色來填補,所以我們要取出最常出現的顏色,也就是取眾數。以下程式碼。

      def get_mode(img):
          ##閾值取眾數
          # bincount():統計非負整數的個數,不能統計浮點數
          counts = np.bincount(img.flatten())
          #counts的index代表出現的數,counts[index]代表出現數的次數
          #今要求counts[index] 排序後最大跟第二大的counts的index(代表眾數跟出現第二多次的數)
          counts_sort = np.argsort(counts) #最後一個元素是counts最大值的index ,倒數第二是二大
          index = counts_sort[-1]
          #以防圖片出現大量黑色面積
          if index <= 100: #出現大量黑色區塊的話,取第二多數
              print('index2:',index)
              index = counts_sort[-2]
              return index
          #否則就return原本的眾數
          print('index_org',index)
          return index
      
      
      image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
      image_mode =get_mode(image)
      #把紅色的mask區域換成眾數
      image[mask3==0]= image_mode
      


      這樣我們就成功把紅線的雜訊去除了。

    • 這邊圖片還是有點模糊,我們做了高斯濾波,讓圖片較平滑,左邊是原圖,右邊是高斯濾波過後的圖,可以看出較平滑

      blur = cv2.GaussianBlur(image,(3,3),0)
      

    • 這邊當時遇到了小問題,當時在選擇灰階和二質化的圖檔做訓練的時候,兩種我們都有嘗試過,但後來發現灰階的資訊較多,所以我們選擇用灰階的方式去訓練圖片。
      二質化               灰階


分割資料夾

  • 處理完圖片,我們將資料集分成train和test,比例7:3,接著就是準備進模型做訓練囉!
import os
import random
import shutil
import os
import shutil

# 分成訓練集跟資料集
src_dir_name = './train/'
target_dir_name = './test/'
test_size = 0.3
labels = set(os.listdir(src_dir_name))

def word_classfier():
    word_list_dir = []
    for i in os.listdir(src_dir_name):
        if i.endswith('.jpg'):
            word_list_dir.append(i.split('.')[0][-1])

    word_list_dir = set(word_list_dir)
    print(word_list_dir)

    for i in os.listdir(src_dir_name):
        if i.endswith('.jpg'):
            if i.split('.')[0][-1] in word_list_dir:
                try:
                    os.mkdir(src_dir_name+i.split('.')[0][-1])
                except FileExistsError:
                    pass
                shutil.move(src_dir_name+i,src_dir_name+i.split('.')[0][-1]+'/'+i)

def move_test_data(test_data:list):
    for i in test_data:
        word_subfolder = i.split('.')[0][-1]
        if word_subfolder in labels:
            print(src_dir_name+word_subfolder+'/'+i)
            try:
                os.mkdir(target_dir_name +word_subfolder)
            except FileExistsError:
                pass
            shutil.move(src_dir_name+word_subfolder+'/'+i,target_dir_name +word_subfolder+'/'+i)
        elif  word_subfolder not in labels:
            word_subfolder = i.split('.')[0][0]
            print(src_dir_name+word_subfolder+'/'+i)
            try:
                os.mkdir(target_dir_name +word_subfolder)
            except FileExistsError:
                pass
            try:
                shutil.move(src_dir_name+word_subfolder+'/'+i,target_dir_name +word_subfolder+'/'+i)
            except FileNotFoundError:
                shutil.move(src_dir_name + word_subfolder + '/' + i, target_dir_name + word_subfolder + '/' + i)

def test_train_split():
    try:
        os.mkdir(target_dir_name)
    except FileExistsError:
        pass
    for i in os.listdir(src_dir_name):
        #每個字的照片數
        dir_length = len(os.listdir(src_dir_name+i))
        #3:7抽樣
        test_size = round(0.3 * dir_length)
        test_data = random.sample(os.listdir(src_dir_name+i), k=test_size)
        move_test_data(test_data)

if __name__ == '__main__':
    # 把字分類成800個資料夾
    word_classfier()
    # 分成訓練集跟測試集
    test_train_split() 


今日小結

  • 通過HSV和OpenCV的結合,可以處理很多圖片的雜訊,我們或許不是處理的最完美,但這次比賽讓我們學到用OpenCV,很有趣,有很多可塑性。做影像辨識不可或缺的一項套件之一。
  • 處理完資料,就要準備丟入模型訓練了,今天就先到這邊吧!!
  • 小弟初學OpenCV,若有講錯的地方,請歡迎留言告知我喔><

上一篇
DAY13:玉山人工智慧挑戰賽-中文手寫字辨識(資料前處理)
下一篇
DAY15:玉山人工智慧挑戰賽-中文手寫字辨識(Pytorch 自訂義資料集)
系列文
資料分析及AI深度學習-簡單基礎實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言